#ifndef ARCHIVODEBLOQUES_H_INCLUDED
#define ARCHIVODEBLOQUES_H_INCLUDED
#include "Bloque.h"
#include "../FileAccess/ArchivoBinario.h"
#include <map>

class ArchivoEspacioLibre {
private:
    ArchivoBinario archivo;

    bool leerBloque(const unsigned int Pos, unsigned int & espacioLibre );
    void grabarBloque(unsigned int Pos, unsigned int  espacioLibre );

public:

    ArchivoEspacioLibre(const std::string &filePathName);

    ~ArchivoEspacioLibre();

    void abrir();

    void cerrar();

    void irAlPrincipio();

    bool Siguiente(unsigned int & nroBloque, unsigned int & espacioLibre);

    bool espacioLibre(unsigned int nroBloque, unsigned int & espacioLibre);

    //Actualiza el archivo de metadata con los datos actualizados del bloque
    void actualizarBloque(unsigned int nroBloque,unsigned int espacioLibre);

    unsigned int agregarBloque(unsigned int espacioLibre);


};
/*
template<class T> class ArchivodeBloques{
	 ArchivodeBloques(const std::string &processPath, int tamanio){
		 throw ConfigurationKeyNotFoundException("dummy");
	 }

	    ArchivodeBloques(){
	    	throw ConfigurationKeyNotFoundException("dummy");
	    }
};
*/
//template<class T> class ArchivodeBloques{};

template<class T> class ArchivodeBloques{
private:
    unsigned int size;

	ArchivoBinario archivoDat;
    ArchivoEspacioLibre archivoEl;

    //Ultimo bloque leido	    ArchivodeBloques(const std::string &processPath):size(512),

    BloqueRegVar<T> bloque;

    static const float FillFactor=0.9;
    unsigned int capacidadBloque;

    bool buscarRegistro(const T & registro_bus, unsigned int & numBloque){

    	this->archivoDat.irAlPrincipio();
		T registro;
		bool encontrado = false;

		while (!encontrado && archivoDat.leerBloque(size,this->bloque.getrawData()))
			while(!encontrado && this->bloque.siguiente(registro))
				if (registro ==registro_bus)
					encontrado=true;


		if (encontrado){
			numBloque = (unsigned int)(this->archivoDat.getPosLectura()/size);
			return true;
		}
		else
			return false;
    }


    void agregarRegistro_Interno(const T & registro, unsigned int tamanio){
    	unsigned int numBloque;
    		unsigned int espacioLibre;
    		bool encontrado=false;
    		//BloqueRegVar<IT,size> bloque;



            this->archivoEl.irAlPrincipio();
            while (!encontrado && this->archivoEl.Siguiente(numBloque,espacioLibre))
            	if (espacioLibre - (1-this->FillFactor)*this->capacidadBloque > tamanio)
            		encontrado = true;



        	//solamente leo de disco si el bloque no esta vacío
            if (encontrado && (espacioLibre < this->capacidadBloque))
    			//Cargo el bloque actual desde disco
    			archivoDat.leerBloque(numBloque,size,this->bloque.getrawData());
    		else
    			//blanqueo el bloque actual en RAM
    			this->bloque.setCantRegs(0);

    		//Agrego el registro al bloque
            this->bloque.agregarRegistro(registro,tamanio);

            if (encontrado){
            	//Actualizo el bloque y el archivo de espacio libre
            	archivoDat.grabarBloque(numBloque,size,this->bloque.getrawData());
            	archivoEl.actualizarBloque(numBloque,espacioLibre-tamanio-1);
            }
            else{
            	//Guardo un nuevo bloque al final del archivo de datos y de espacio libre
            	archivoDat.agregarBloque(size,this->bloque.getrawData());
            	archivoEl.agregarBloque(size-tamanio-1);
            }



    }

public:

    ArchivodeBloques(const std::string &processPath, int tamanio):size(tamanio),
		archivoDat(processPath+".dat"),archivoEl(processPath+".el"),bloque(tamanio)
		, capacidadBloque(size-sizeof(unsigned char)){}

    ArchivodeBloques(const std::string &processPath):size(512),
   		archivoDat(processPath+".dat"),archivoEl(processPath+".el"), bloque(512)
   		, capacidadBloque(size-sizeof(unsigned char)){}

    void crear(){
    	this->archivoEl.abrir();
    	this->archivoEl.agregarBloque(this->capacidadBloque);
    	this->archivoEl.cerrar();

    	this->archivoDat.abrir();
    	this->archivoDat.agregarBloque(size,this->bloque.getrawData());
    	this->archivoDat.cerrar();
    }
    void agregarRegistro(const T & registro, unsigned int tamanio){

    	bool esNuevo;
		this->archivoDat.abrir(esNuevo);
		if (esNuevo){
			this->archivoDat.cerrar();
			this->crear();
			this->archivoDat.abrir();
		}
		this->archivoEl.abrir();
		this->agregarRegistro_Interno(registro,tamanio);

		this->archivoDat.cerrar();
		this->archivoEl.cerrar();



    }


    void eliminarRegistro(const T & registro, OPERACIONBLOQUE & resultado ){
    	unsigned int numBloque;
    //	unsigned char posicion;
    	unsigned int libre;
    	unsigned int tamBaja;

    	bool esNuevo;
		this->archivoDat.abrir(esNuevo);
		if (esNuevo){
			this->archivoDat.cerrar();
			this->crear();
			this->archivoDat.abrir();
		}
    	if (this->buscarRegistro(registro,numBloque)){
    		this->archivoEl.abrir();
    		tamBaja = this->bloque.quitarRegistro(this->bloque.posicionActual);
    		this->archivoDat.grabarBloque(numBloque,size,this->bloque.getrawData());
    		this->archivoEl.espacioLibre(numBloque,libre);
    		this->archivoEl.actualizarBloque(numBloque,libre+tamBaja+1);
    		this->archivoEl.cerrar();
    	}
    	else
    	{
    		//todo: throw Exception
    	}
    	this->archivoDat.cerrar();
    }

    void actualizarRegistro(const T & registro, unsigned int tamanioNuevo, OPERACIONBLOQUE & resultado ){
    	unsigned int numBloque;
    	//unsigned char posicion;
    	unsigned int libre;
    	unsigned char tamBaja;

    	bool esNuevo;
		this->archivoDat.abrir(esNuevo);
		if (esNuevo){
			this->archivoDat.cerrar();
			this->crear();
			this->archivoDat.abrir();
		}
		this->archivoEl.abrir();
    	if (this->buscarRegistro(registro,numBloque)){
    		tamBaja = this->bloque.quitarRegistro(this->bloque.posicionActual);
    		this->bloque.agregarRegistro(registro,resultado);
    		if (resultado == FALLA){
        		this->archivoDat.grabarBloque(numBloque,size,this->bloque.getrawData());
        		this->archivoEl.espacioLibre(numBloque,libre);
        		this->archivoEl.actualizarBloque(numBloque,libre+tamBaja+1);
        		this->agregarRegistro_Interno(registro,tamanioNuevo);
    		}
    		else{
        		this->archivoDat.grabarBloque(numBloque,size,this->bloque.getrawData());
        		this->archivoEl.espacioLibre(numBloque,libre);
        		this->archivoEl.actualizarBloque(numBloque,libre+tamBaja-tamanioNuevo);

    		}
    	}
    	else
    	{
    		//todo: throw Exception
    	}
    	this->archivoDat.cerrar();
    	this->archivoEl.cerrar();

    }

    void listarContenido(std::ostream & salida){
    	BloqueRegVar<T> bloqueTemp(size);


    	bool esNuevo;
		this->archivoDat.abrir(esNuevo);
		if (esNuevo){
			this->archivoDat.cerrar();
			this->crear();
			this->archivoDat.abrir();
		}

    	this->archivoDat.irAlPrincipio();


    	while (archivoDat.leerBloque(size,bloqueTemp.getrawData())){
    		T registro;
    		bloqueTemp.irAlPrincipio();
    		while(bloqueTemp.siguiente(registro)){
    			salida << registro<<std::endl;
    			salida << std::endl;
    		}



    	}
       	this->archivoDat.cerrar();


    }


};




#endif // ARCHIVODEBLOQUES_H_INCLUDED
